﻿/*  @File        : CFormCtrl.h
 *  @Brief       : Form窗口通用类
 *  @Details     : 包括有自定义标题栏(CTitleBar),自定义主界面(CMainWindow),自定义窗口CWidget,自定义对话框CDialog,自定义IP地址输入框,虚拟键盘等
 *  @Author      : HaoQinghui
 *  @Date        : 2022-04-26 17:33:21
 *  @Version     : v1.0
 *  @Copyright   : Copyright By HaoQinghui, All Rights Reserved
 *
 **********************************************************
 *
 *  @Attention   :
 *  @note 修改日志:
 *  <table>
 *  <tr><th>Date       <th>Version  <th>Author      <th>Description
 *  <tr><td>2022-04-26 <td>1.0      <td>HaoQinghui  <td>创建初始版本
 *  </table>
 *
 **********************************************************/

#ifndef CFORMCTRL_H
#define CFORMCTRL_H

#ifndef MainFormDefine
// 定义主界面
#define MainFormDefine      static MainWindow *instance() { return static_cast<MainWindow*>(s_instance); }
#endif
#ifndef MainForm
// 使用主界面
#define MainForm            MainWindow::instance()
#endif

#include <QtCore>
// **判断是否为带有GUI或控制台程序
#ifdef QT_GUI_LIB
#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
#include <QtWidgets>
#endif
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
#include <QtCore5Compat>
#endif
#else
#error "HQH: Not Gui! Not Widget!"
#error "Can't find gui lib. Open *.pro file add 'QT+= gui' supported"
#endif

// #define MOUSE_MOVE_ENABLED_WIDGET           // Widget - 窗口的鼠标移动使能
// #define MOUSE_MOVE_ENABLED_MAINWINDOW       // MainWindow - 窗口的鼠标移动使能

/**@ClassName   : 自定义标题栏
   @Description : 可以移动,单独设置样式表,背景色等功能;实例化了本类,必须手动调用connectBtnClicked()函数
   @Author      : HaoQinghui
   @Data        : 2024-04-03 08:44:49
*/

#ifdef hqh
class Q_DECL_EXPORT CTitleBar : public QWidget
#else
class CTitleBar : public QWidget
#endif
{
    Q_OBJECT
public:
    explicit CTitleBar(QWidget *parent, bool hasBtnMin, bool hasBtnMax);
    virtual ~CTitleBar();

    /**@brief 设置标题栏的背景颜色 */
    void            setTitleBackground(const QColor &bgColor);
    /**@brief 设置标题栏的图标 */
    void            setTitleIcon(const QIcon &ico, const QSize &icoSize = QSize(18, 18));
    /**@brief 设置标题栏的文字大小 */
    void            setTitleFont(const QFont &font) { m_titleText->setFont(font); }
    /**@brief 设置标题栏的文字大小 */
    void            setTitleFontSize(int fontSize) { m_titleText->setFont(QFont(m_titleText->font().family(), fontSize)); }

    /**@brief 工具栏中添加一条分割线 */
    void            addNavLine();
    /**@brief 工具栏中添加一个按钮 */
    QToolButton*    addNavButton(const QString &btnTxt, bool textBesideIcon, int minHight = 40);
    void            insertRightLine();
    void            insertRightButton(QWidget *formCtrl);

    /**@brief 检测父界面是否最大化 */
    void            checkParentWidgetIsMax();
    /**@brief       : 为标题栏做格式化处理
     * @details     : 详细说明
     * @author      : HaoQinghui
     * @param       : titleBar: 自定义的标题栏控件
     *              : layoutMain: 标题栏的上层布局
     *              : widgetWorkspace: 工作区界面
     * @return      : 返回值说明
     * @output      :
     * @callgraph   :
     * @date        : 2024-05-02 00:45:32
     * @modify      : 日期加修改描述
     */
    static void     setTitleFormat(CTitleBar *titleBar, QVBoxLayout *layoutMain, QWidget *widgetWorkspace);

    QPushButton         *m_btnMin;                  //!< 最小化btn
    QPushButton         *m_btnMax;                  //!< 最大化btn
    QPushButton         *m_btnClose;                //!< 关闭btn

private:
    void            mousePressEvent(QMouseEvent *event) override;
    void            mouseMoveEvent(QMouseEvent *event) override;
    void            mouseReleaseEvent(QMouseEvent *event) override;

private:
    // 移动窗口的变量;
    bool                m_isPressed;                //!< 是否正被选中
    QPoint              m_mouseStartPoint;          //!< 获得鼠标拖拽的初始位置

    QWidget             *m_mainView;                //!< 本界面的停靠窗口

    QLabel              *m_labIco;                  //!< Icon图标
    QLabel              *m_titleText;               //!< 标题栏文本

    QWidget             *m_navWidget;
    QButtonGroup        *m_navBtnGroup;
    QHBoxLayout         *m_layoutNavBtn;            //!< 标题栏的导航按钮布局

    QHBoxLayout         *m_layoutRightBtn;          //!< 标题栏的右侧按钮布局

};

/**@ClassName   : 可移动的Widget窗口
   @Description : 根据设置宏可以移动,但如果有父窗体则不能移动
   @Author      : HaoQinghui
   @Data        : 2024-04-03 10:37:17
*/
#ifdef hqh
class Q_DECL_EXPORT CWidget : public QWidget
#else
class CWidget : public QWidget
#endif
{
    Q_OBJECT
public:
    explicit CWidget(QWidget *parent = nullptr);
    ~CWidget();

    /**@brief 重置界面数据 - 用于重新打开对话框(重写) */
    virtual void    reloadFormData() = 0;
    /**@brief 控件重新翻译(重写) */
    virtual void    retranslateUi() = 0;
protected:
    /**@brief 初始化组件(重写) */
    virtual void    initializeComponent();

    /**@brief 添加标题栏 */
    void            addTitleBar(bool hasBtnMax, QWidget *widgetWorkspace);

    /**@brief       : 设置表格自动列宽
     * @details     : 界面大小变动时, 调整表格的宽度, 并将某一列调整到最大列表
     * @author      : HaoQinghui
     * @param       : tableWidget: 表格
     *              : widestColId: 最宽的列ID
     *              : defColWidth: 默认的列宽
     * @return      : null
     * @date        : 2024-04-10 10:54:43
     * @modify      : 日期加修改描述
     */
    void            setTableWidgetAutoColWidth(QTableWidget *tableWidget, int widestColId, int defColWidth);
    void            resizeEvent(QResizeEvent *event) override;

#ifdef MOUSE_MOVE_ENABLED_WIDGET
    // 拖拽窗口
    void            mousePressEvent(QMouseEvent *event) override;
    void            mouseMoveEvent(QMouseEvent *event) override;
    void            mouseReleaseEvent(QMouseEvent *event) override;

private:
    // 拖拽窗口
    bool                m_isPressed;                //!< 是否正被选中
    QPoint              m_mouseStartPoint;          //!< 获得鼠标拖拽的初始位置
#endif
#if defined (MOUSE_MOVE_ENABLED_WIDGET) || defined (MOUSE_MOVE_ENABLED_MAINWINDOW)
    /**@brief 过滤某些控件,放置点击时误触发窗口的拖拽 */
    bool            eventFilter(QObject *obj, QEvent *event) override;

private:
    // 过滤某些控件,放置点击时误触发窗口的拖拽
    QList<QWidget*>     m_ctrlList;                 //!< 事件过滤器列表
#endif
protected:
    CTitleBar           *m_titleBar;                //!< 设置标题栏

private:
    QList<std::tuple<QTableWidget*, int , int> > m_tableWidgetAutoList;

    friend class CDialog;
    friend class CMainWindow;
    /**@brief 获取一个窗体上的所有下拉框和菜单 */
    static QList<QWidget*> getAllComboAndMenuCtrl(QWidget* parent);
    /**@brief   设置TableWidget的自动调整列
     * @param   tableWidget: 输入项
     *          widestColId: 最宽列序号
     *          widestColWidth: 默认的列宽
     * @return
     */
    static void     autoTableWidgetColWidth(QTableWidget *tableWidget, int widestColId, int defColWidth);

};

/**@ClassName   : 可移动的QMainWindow
   @Description : 项目唯一的主界面,提供了对象单实例、增加标题栏、软件皮肤下拉框、帮助按钮等选择项
   @Author      : HaoQinghui
   @Data        : 2024-04-03 08:44:49
*/
#ifdef hqh
class Q_DECL_EXPORT CMainWindow : public QMainWindow
#else
class CMainWindow : public QMainWindow
#endif
{
    Q_OBJECT
public:
    explicit CMainWindow(QWidget *parent = nullptr);
    ~CMainWindow();

    /**@brief 程序单实例运行 */
    void            setAppSingleton();

    /**@brief       : 消息提示 - 对话框
     * @details     : 需要外部对象的信号来关联
     * @author      : HaoQinghui
     * @param       : isInfoOrErr: True:Info消息;False:Error消息
     *              : msgTitle: 消息标题
     *              : msgData: 消息内容
     * @return      : null
     * @date        : 2024-04-10 10:54:43
     * @modify      : 日期加修改描述
     */
    void            showMessageDlg(bool isInfoOrErr, const QString &msgTitle, const QString &msgData) {
        QMetaObject::invokeMethod(this, "onShowMessage",
                                  Q_ARG(bool, true),
                                  Q_ARG(bool, isInfoOrErr),
                                  Q_ARG(const QString &, msgTitle),
                                  Q_ARG(const QString &, msgData),
                                  Q_ARG(int, 0));
    }
    /**@brief       : 消息提示 - 状态栏
     * @details     : 需要外部对象的信号来关联
     * @author      : HaoQinghui
     * @param       : msgData: 消息内容
     *              : timeout_ms: 状态栏提示时有效,若为0则状态栏提示一直显示
     * @return      : null
     * @date        : 2024-04-10 10:54:43
     * @modify      : 日期加修改描述
     */
    void            showMessageStatus(const QString &msgData, int timeout_ms = 0) {
        QMetaObject::invokeMethod(this, "onShowMessage",
                                  Q_ARG(bool, false),
                                  Q_ARG(bool, true),
                                  Q_ARG(const QString &, nullptr),
                                  Q_ARG(const QString &, msgData),
                                  Q_ARG(int, timeout_ms));
    }

protected:
    QSettings           *m_settings;

    CTitleBar           *m_titleBar;                //!< 设置标题栏
    QToolBar            *m_toolBar;                 //!< 设置工具栏

    /**@brief 初始化组件(重写) */
    virtual void    initializeComponent();
    /**@brief 过滤某些控件,放置点击时误触发窗口的拖拽 */
    void            resetIconAndTitle();

    /**@brief 调试员快捷键:Ctrl+Alt+Shift + U */
    virtual void    debugShortcut() {}
    /**@brief 超级管理员快捷键:Ctrl+Alt+Shift + P */
    virtual void    superShortcut() {}

    /**@brief 添加标题栏 */
    void            addTitleBar(bool hasBtnMax, QWidget *widgetWorkspace);

    /**@brief 添加工具栏 */
    void            addToolBarTop();

    /**@brief 添加一个切换皮肤的下拉框 */
    QComboBox*      addComboBoxSkin(bool isChinese = true);

    /**@brief 添加pdf帮助文档按钮 */
    QPushButton*    addHelpPdfButton();

    /**@brief       : 设置表格自动列宽
     * @details     : 界面大小变动时, 调整表格的宽度, 并将某一列调整到最大列表
     * @author      : HaoQinghui
     * @param       : tableWidget: 表格
     *              : widestColId: 最宽的列ID
     *              : defColWidth: 默认的列宽
     * @return      : null
     * @date        : 2024-04-10 10:54:43
     * @modify      : 日期加修改描述
     */
    void            setTableWidgetAutoColWidth(QTableWidget *tableWidget, int widestColId, int defColWidth);

    void            resizeEvent(QResizeEvent *event) override;

    /**@brief 过滤某些控件,放置点击时误触发窗口的拖拽
        提供超级管理员接口*/
    bool            eventFilter(QObject *obj, QEvent *event) override;

#ifdef MOUSE_MOVE_ENABLED_MAINWINDOW
    // 拖拽窗口
    void            mousePressEvent(QMouseEvent *event) override;
    void            mouseMoveEvent(QMouseEvent *event) override;
    void            mouseReleaseEvent(QMouseEvent *event) override;

private:
    // 拖拽窗口
    bool                m_isPressed;                //!< 是否正被选中
    QPoint              m_mouseStartPoint;          //!< 获得鼠标拖拽的初始位置
    // 过滤某些控件,放置点击时误触发窗口的拖拽
    QList<QWidget*>     m_ctrlList;                 //!< 事件过滤器列表
#endif

private Q_SLOTS:
    /**@brief       : 错误消息提示槽
     * @details     : 需要外部对象的信号来关联
     * @author      : HaoQinghui
     * @param       : isDlg: 是否对话框
     *              : isInfoOrErr: True:Info消息;False:Error消息
     *              : msgTitle: 消息标题
     *              : msgData: 消息内容
     *              : timeout_ms: 状态栏提示时有效,若为0则状态栏提示一直显示
     * @return      : null
     * @date        : 2024-04-10 10:54:43
     * @modify      : 日期加修改描述
     */
    void            onShowMessage(bool isDlg, bool isInfoOrErr, const QString &msgTitle, const QString &msgData, int timeout_ms = 0);

private:
    QSharedMemory       *m_sharedMemory;            //!< 单实例运行

    QList<std::tuple<QTableWidget*, int , int> > m_tableWidgetAutoList;

};

/**@ClassName   : 可移动的QDialog
   @Description : 根据设置宏可以移动
   @Author      : HaoQinghui
   @Data        : 2024-04-03 08:44:49
*/
#ifdef hqh
class Q_DECL_EXPORT CDialog : public QDialog
#else
class CDialog : public QDialog
#endif
{
    Q_OBJECT
public:
    explicit CDialog(QWidget *parent = nullptr);
    ~CDialog();

    static void     MessageBoxInfo(const QString &text)
    { MessageBoxInfo(nullptr, text, nullptr, QMessageBox::Ok); }
    static void     MessageBoxInfo(const QString &title, const QString &text)
    { MessageBoxInfo(title, text, nullptr, QMessageBox::Ok); }
    // 提示对话框 - 自定义
    static void     MessageBoxInfo(const QString &title, const QString &text, const QString &btnText, QMessageBox::StandardButtons btnType = QMessageBox::Ok);

    static void     MessageBoxError(const QString &text)
    { MessageBoxError(nullptr, text, nullptr, QMessageBox::Ok); }
    static void     MessageBoxError(const QString &title, const QString &text)
    { MessageBoxError(title, text, nullptr, QMessageBox::Ok); }
    // 错误对话框 - 自定义
    static void     MessageBoxError(const QString &title, const QString &text, const QString &btnText, QMessageBox::StandardButtons btnType = QMessageBox::Ok);

    static int      MessageBoxWaiting(const QString &text, int defaultBtnId, int closeSec)
    { return MessageBoxWaiting(nullptr, text, defaultBtnId, closeSec, nullptr, nullptr, QMessageBox::StandardButtons(QMessageBox::Ok | QMessageBox::Cancel)); }
    // 等待对话框 - 自定义
    static int      MessageBoxWaiting(const QString &title, const QString &text, int defaultBtnId, int closeSec,
                                      const QString &btn1txt, const QString &btn2txt, QMessageBox::StandardButtons btnType);

    static int      MessageBoxQuestion(const QString &text)
    { return MessageBoxQuestion(nullptr, text, nullptr, nullptr); }
    static int      MessageBoxQuestion(const QString &title, const QString &text)
    { return MessageBoxQuestion(title, text, nullptr, nullptr); }
    // 询问对话框(自定义按钮组) - 自定义
    static int      MessageBoxQuestion(const QString &title, const QString &text, const QString &btn1txt, const QString &btn2txt);
    // 询问对话框(包含三个按钮) - 自定义
    static int      MessageBoxQuestions(const QString &title, const QString &text, const QStringList &btnListTxt);

    // Input输入框 - 自定义
    static QString  getText(const QString &title, const QString &label,
                            QLineEdit::EchoMode echo = QLineEdit::Normal,
                            const QString &text = QString(), bool *ok = nullptr);

    static QString  getItem(const QString &title, const QString &label,
                            const QStringList &items, int current = 0, bool *ok = nullptr);

    static int      getInt(const QString &title, const QString &label, int value = 0,
                           int minValue = -2147483647, int maxValue = 2147483647,
                           int step = 1, bool *ok = nullptr);

    static double   getDouble(const QString &title, const QString &label, double value = 0,
                              double minValue = -2147483647, double maxValue = 2147483647,
                              int decimals = 1, bool *ok = nullptr, double step = 1);

protected:    
    /**@brief 添加标题栏 */
    void            addTitleBar(QWidget *widgetWorkspace);

    /**@brief 初始化组件(重写) */
    virtual void    initializeComponent();

private slots:
    void            onTimeout();

private:
    int             MessageBoxEx_Ex(QMessageBox::Icon msgType, const QString &title, const QString &text, QMessageBox::StandardButtons btnType,
                                    const QStringList &btnsTxt, int defaultBtnId, int closeSec);
    QWidget*        MessageInputBox(QWidget *widget, const QString &title, const QString &label);

    /**@brief 设置标题、图标、msg文本 */
    void            setLabsImgAndTxt(QMessageBox::Icon msgType, const QString &title);
    /**@brief 设置按钮文本 */
    void            setButtonsText(QMessageBox::StandardButtons btnType, const QStringList &btnsTxt, int defaultBtnId);

    void            resizeEvent(QResizeEvent *event) override;

private:
    int                 m_closeSec;                 //!< 自动关闭窗口的秒表
    CTitleBar           *m_titleBar;                //!< 自定义的标题栏

    QVBoxLayout         *m_layoutContent;           //!< 对话框的显示内容，包含下面的按钮
    QLabel              *m_labMsg;
    QButtonGroup        *m_btnGroup;                //!< 对话框的按钮组

};

/**@ClassName   : 重写int输入过滤器
   @Description : 解决原有过滤器最大范围的问题. 如设置1~100, 最大值可输入999
   @Author      : HaoQinghui
   @Data        : 2024-04-03 08:44:49
*/
#ifdef hqh
class Q_DECL_EXPORT CustomIntValidator : public QIntValidator
#else
class CustomIntValidator : public QIntValidator
#endif
{
    Q_OBJECT
public:
    explicit CustomIntValidator(QObject *parent = nullptr) : QIntValidator(parent) {}
    CustomIntValidator(int bottom, int top, QObject *parent) : QIntValidator(bottom, top, parent) {}
    void setRange(int bottom, int top) { QIntValidator::setRange(bottom, top); }
    virtual State validate(QString &s, int &n) const override;
    virtual void fixup(QString &input) const override;

};

/**@ClassName   : 重写int输入过滤器
   @Description : 解决原有过滤器最大范围的问题. 如设置1~100, 最大值可输入999999.11
   @Author      : HaoQinghui
   @Data        : 2024-04-03 08:44:49
*/
#ifdef hqh
class Q_DECL_EXPORT CustomDoubleValidator : public QDoubleValidator
#else
class CustomDoubleValidator : public QDoubleValidator
#endif
{
    Q_OBJECT
public:
    explicit CustomDoubleValidator(QObject *parent = nullptr) : QDoubleValidator(parent) {}
    CustomDoubleValidator(double bottom, double top, int decimals, QObject *parent)
        : QDoubleValidator(bottom, top, decimals, parent) {}
    void setRange(double bottom, double top, int decimals = 0) { QDoubleValidator::setRange(bottom, top, decimals); }
    virtual State validate(QString &s, int &) const override;
    virtual void fixup(QString &input) const override;

};

/**@brief  IP地址输入框*/
#ifdef hqh
class Q_DECL_EXPORT IPAddressLineEdit : public QLineEdit
#else
class IPAddressLineEdit : public QLineEdit
#endif
{
    Q_OBJECT
public:
    explicit IPAddressLineEdit(QWidget *parent);
    QString textIP4() const;//获取控件文本
    void setTextIP4(const QString &addr);//设置控件文本
    void setReadOnly(bool isReadOnly);

private:
    QLineEdit       *m_l1;
    QLineEdit       *m_l2;
    QLineEdit       *m_l3;
    QLineEdit       *m_l4;
    void        initForEdit(QLineEdit *edit);
    bool        eventFilter(QObject* object, QEvent* event) override;

};

/**@brief  虚拟数字键盘*/
#ifdef hqh
class Q_DECL_EXPORT VirtualNumKeyboard : public CDialog
#else
class VirtualNumKeyboard : public CDialog
#endif
{
    Q_OBJECT
public:
    explicit VirtualNumKeyboard(QWidget* parent);
    double getValue() const { return m_val; }
    void setValue(const double val) { m_val = val; m_txtVal->setText(QString::number(val)); }

    void setRange(int minVal, int maxVal) { m_labMinVal->setNum(qMin(minVal, maxVal));m_labMaxVal->setNum(qMax(minVal, maxVal)); }
    void setRange(double minVal, double maxVal) { m_labMinVal->setNum(qMin(minVal, maxVal));m_labMaxVal->setNum(qMax(minVal, maxVal)); }

private:
    QLineEdit       *m_txtVal;
    QLabel          *m_labMinVal;
    QLabel          *m_labMaxVal;
    double          m_val;
    QPushButton     *m_btnDel;

    void    updateValue(const QString &btn_txt);

    /**@brief 初始化组件(重写) */
    void    initializeComponent() override;

};

#endif // CFORMCTRL_H
